{
  "openapi": "3.1.0",
  "info": {
    "title": "API Mở Tài Nguyên Dùng Chung",
    "description": "Quản lý các tài nguyên dùng chung (proxy, nhóm và thẻ) thông qua API mở của MoreLogin.\n\n**URL cơ sở**: `https://api.morelogin.com`\n\nXác thực được thực hiện thông qua mã xác thực OAuth2 Bearer. Xem [Xác thực](../Getting%20Started/authentication.md) để biết chi tiết.\n",
    "version": "2026-09-05"
  },
  "tags": [
    {
      "name": "Proxy Management",
      "description": "Tạo, cập nhật, xóa và liệt kê các cấu hình proxy"
    },
    {
      "name": "Group Management",
      "description": "Tạo, cập nhật, xóa và liệt kê các nhóm môi trường"
    },
    {
      "name": "Tag Management",
      "description": "Tạo, cập nhật, xóa và liệt kê các thẻ môi trường"
    },
    {
      "name": "Webhook Management",
      "description": "Cấu hình endpoint callback của nhóm để nhận sự kiện webhook"
    }
  ],
  "paths": {
    "/proxyInfo/page": {
      "post": {
        "summary": "Lấy danh sách máy chủ proxy",
        "deprecated": false,
        "description": "Liệt kê theo trang các cấu hình proxy của nhóm.\n- **Tác động**: không có. Chỉ đọc. An toàn khi thử lại với thời gian chờ tăng dần có giới hạn.\n- **Lưu ý**: mật khẩu không được trả về cho thành viên thường, cũng như với proxy cloud và proxy động. Proxy động xuất hiện ở đây nhưng được quản lý ở nơi khác và không thể sửa qua các endpoint proxy.",
        "operationId": "queryProxyInfoPageUsingPOST",
        "tags": [
          "Proxy Management"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "pattern": "^\\d+$",
                    "description": "ID proxy tùy chọn để khớp chính xác. Gửi ID 64 bit dưới dạng chuỗi thập phân để tránh mất độ chính xác. Bỏ qua để không lọc theo ID. Các bộ lọc khác và phân trang vẫn áp dụng.",
                    "example": "1800000000000101"
                  },
                  "pageNo": {
                    "type": "integer",
                    "description": "Số trang hiện tại (bắt đầu từ 1)",
                    "example": 1
                  },
                  "pageSize": {
                    "type": "integer",
                    "description": "Số mục trên mỗi trang",
                    "example": 10
                  },
                  "isCloudPhoneProxy": {
                    "type": "boolean",
                    "description": "Proxy có dùng cho cloud phone hay không.\n- `true`: Loại trừ IP nội bộ hoặc riêng tư (ví dụ: 127.0.0.1, 360, pia, các dải riêng)\n- `false`: Trả về tất cả proxy\n",
                    "example": false
                  },
                  "proxyCategoryType": {
                    "type": "integer",
                    "description": "Loại danh mục proxy:\n- `1` = Nền tảng đám mây\n- `2` = IP tự sở hữu\n",
                    "example": 2
                  },
                  "proxyIp": {
                    "type": "string",
                    "description": "Lọc theo địa chỉ IP của proxy (khớp một phần)",
                    "example": ""
                  },
                  "proxyName": {
                    "type": "string",
                    "description": "Lọc theo tên proxy (khớp một phần)",
                    "example": ""
                  },
                  "proxyProviders": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "description": "Danh sách ID Loại proxy. Giá trị được hỗ trợ:\n- `0` = Không có\n- `4` = Oxylabs\n- `5` = Proxys.io\n- `6` = GeoSurf\n- `7` = Luminati\n- `8` = Lumauto\n- `9` = Oxylabsauto\n- `10` = Trojan\n- `11` = Shadowsocks\n- `12` = vmess\n- `13` = ABCPROXY\n- `14` = LunaProxy\n- `15` = IPHTML\n- `16` = PiaProxy\n- `17` = 922S5\n",
                    "example": []
                  },
                  "proxyStatus": {
                    "type": "integer",
                    "description": "Trạng thái gán proxy:\n- `0` = Bình thường\n- `1` = Chưa gán\n- `2` = Đang nâng cấp\n- `3` = Đã hết hạn\n- `4` = Sắp hết hạn  \n> Lưu ý: proxy tự sở hữu luôn dùng trạng thái `1`.\n",
                    "example": 1
                  },
                  "proxyCheckStatus": {
                    "type": "integer",
                    "description": "Trạng thái kiểm tra proxy:\n- `0` = Đang chờ\n- `1` = Thành công\n- `2` = Thất bại\n- `3` = Lỗi không xác định\n",
                    "example": 1
                  },
                  "proxyTypes": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "description": "Các loại giao thức proxy:\n- `0` = HTTP\n- `1` = HTTPS\n- `2` = SOCKS5\n- `3` = SSH\n",
                    "example": []
                  }
                },
                "required": []
              },
              "example": {
                "id": "1800000000000101",
                "pageNo": 1,
                "pageSize": 10,
                "isCloudPhoneProxy": false,
                "proxyCategoryType": 2,
                "proxyIp": "",
                "proxyName": "",
                "proxyProviders": [],
                "proxyStatus": 1,
                "proxyCheckStatus": 1,
                "proxyTypes": []
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Thành công. `data.dataList` chứa các bản ghi của trang được yêu cầu và là mảng rỗng khi không có kết quả khớp; `data.total`, `data.current` và `data.pages` mô tả phân trang.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "description": "Mã kết quả. `0` là thành công; các mã khác biểu thị ngoại lệ.",
                      "type": "integer"
                    },
                    "msg": {
                      "description": "Thông báo lỗi",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "current": {
                          "type": "string"
                        },
                        "dataList": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "expiryTime": {
                                "description": "Thời điểm hết hạn",
                                "type": "string"
                              },
                              "id": {
                                "description": "Khóa chính",
                                "type": "string"
                              },
                              "proxyCategoryType": {
                                "description": "Loại proxy: 1 - proxy của nền tảng; 2 - proxy tự sở hữu",
                                "type": "integer"
                              },
                              "proxyCheckStatus": {
                                "description": "Trạng thái kiểm tra: 0 - đang chờ kiểm tra, 1 - kiểm tra thành công, 2 - kiểm tra thất bại, 3 - lỗi không xác định",
                                "type": "integer"
                              },
                              "state": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Bang hoặc tỉnh của proxy. Ưu tiên vị trí IP đầu ra được phát hiện, sau đó dùng vị trí cấu hình; có thể là null hoặc chuỗi rỗng.",
                                "example": "California"
                              },
                              "city": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Thành phố của proxy. Ưu tiên vị trí IP đầu ra được phát hiện, sau đó dùng vị trí cấu hình; có thể là null hoặc chuỗi rỗng.",
                                "example": "Los Angeles"
                              },
                              "proxyPort": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "format": "int32",
                                "description": "Cổng kết nối proxy, không suy ra từ tên proxy. Có thể là null nếu không có.",
                                "example": 1080
                              },
                              "proxyIp": {
                                "description": "IP của proxy",
                                "type": "string"
                              },
                              "exportIp": {
                                "description": "IP xuất",
                                "type": "string"
                              },
                              "countryCode": {
                                "description": "countryCode",
                                "type": "string"
                              },
                              "proxyName": {
                                "description": "Tên proxy",
                                "type": "string"
                              },
                              "proxyProvider": {
                                "description": "Loại proxy: giá trị mặc định 0 - không có 4-Oxylabs 5-Proxys.io 7-Luminati 8-Lumauto 9-Oxylabsauto 10-Trojan，11-Shadowsocks 13-ABCPROXY 14-LunaProxy 15-IPHTML 16-PiaProxy 17-922S5\n",
                                "type": "integer"
                              },
                              "proxyType": {
                                "description": "Loại proxy: 0 - http, 1 - https, 2 - socks5, 3 - ssh",
                                "type": "integer"
                              },
                              "username": {
                                "description": "Tên người dùng proxy",
                                "type": "string"
                              },
                              "password": {
                                "description": "Mật khẩu proxy",
                                "type": "string"
                              },
                              "refreshUrl": {
                                "description": "URL làm mới proxy",
                                "type": "string"
                              }
                            }
                          }
                        },
                        "pages": {
                          "type": "string"
                        },
                        "total": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "current",
                        "dataList",
                        "pages",
                        "total"
                      ]
                    },
                    "requestId": {
                      "description": "ID yêu cầu của thao tác",
                      "type": "string"
                    }
                  },
                  "required": [
                    "code",
                    "msg",
                    "data",
                    "requestId"
                  ]
                },
                "example": {
                  "code": 0,
                  "msg": null,
                  "data": {
                    "current": "1",
                    "dataList": [
                      {
                        "expiryTime": "2026-09-01 12:00:00",
                        "id": "1673823102599682",
                        "proxyCategoryType": 1,
                        "proxyCheckStatus": 1,
                        "state": "California",
                        "city": "Los Angeles",
                        "proxyPort": 1080,
                        "proxyIp": "string",
                        "exportIp": "9222",
                        "countryCode": "string",
                        "proxyName": "example-name",
                        "proxyProvider": 1,
                        "proxyType": 1,
                        "username": "example-name",
                        "password": "string",
                        "refreshUrl": "https://example.com/resource"
                      }
                    ],
                    "pages": "1",
                    "total": "1"
                  },
                  "requestId": "0d8b6f1c-9f4e-4d02-9d1a-7c3f5a1b2c3d"
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/proxyInfo/add": {
      "post": {
        "summary": "Thêm máy chủ proxy",
        "deprecated": false,
        "description": "Tạo một cấu hình proxy trong nhóm.\n- **Điều kiện tiên quyết**: `proxyPort` là bắt buộc và phải nằm trong 1–65535, còn `proxyProvider` phải là một dịch vụ thiết bị được nhận diện.\n- **Tác động**: chèn một bản ghi proxy và trả về ID của nó, sẵn sàng để gán cho các profile. Không có gì được gửi tới Loại proxy.\n- **Hoàn tất**: đồng bộ.\n- **Thử lại**: **không có bước kiểm tra trùng tên hay trùng cấu hình.** Gửi cùng một request body hai lần sẽ tạo ra hai proxy với hai ID. Sau khi hết thời gian chờ, hãy tìm trong `/proxyInfo/page` trước khi gửi lại.\n\nCác mã lỗi nghiệp vụ đã ghi nhận: `14023`. Xem [Error Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi thao tác cũng có thể trả về các mã dùng chung.",
        "operationId": "addUsingPOST",
        "tags": [
          "Proxy Management"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "city": {
                    "type": "string",
                    "description": "Thành phố",
                    "example": ""
                  },
                  "country": {
                    "type": "string",
                    "description": "Mã quốc gia (ISO 3166-1 alpha-2). Chỉ bắt buộc nếu proxyProvider=17.\n",
                    "example": "AD"
                  },
                  "encryptionType": {
                    "type": "integer",
                    "format": "int32",
                    "description": "Loại mã hóa; giá trị hợp lệ là số nguyên từ 1 đến 14.",
                    "example": 14
                  },
                  "ipChangeAction": {
                    "type": "integer",
                    "format": "int32",
                    "description": "Hành động khi IP thay đổi: 0 = Chặn, 1 = Cảnh báo",
                    "example": 1
                  },
                  "ipMonitor": {
                    "type": "boolean",
                    "description": "Bật theo dõi thay đổi IP",
                    "example": false
                  },
                  "password": {
                    "type": "string",
                    "description": "Mật khẩu để xác thực proxy",
                    "example": ""
                  },
                  "proxyIp": {
                    "type": "string",
                    "description": "Địa chỉ IP của proxy",
                    "example": "8.10.22.46"
                  },
                  "proxyName": {
                    "type": "string",
                    "description": "Tên proxy (hỗ trợ chỗ giữ chỗ như {{random}})",
                    "example": "sock5_{{random}}"
                  },
                  "proxyPort": {
                    "type": "integer",
                    "format": "int32",
                    "description": "Số cổng của proxy",
                    "example": 65534
                  },
                  "proxyProvider": {
                    "type": "integer",
                    "format": "int32",
                    "description": "Loại Loại proxy:\n0=HTTP, 1=HTTPS, 2=SOCKS5, 3=SSH, 4=Oxylabs, 5=Proxys.io, 6=GeoSurf, 7=Luminati, 8=Lumauto, 9=Oxylabsauto, 10=Trojan, 11=Shadowsocks, 12=vmess, 13=ABCPROXY, 14=LunaProxy, 15=IPHTML, 16=PiaProxy, 17=922S5.\nQuy tắc đặc biệt:\n- Nếu proxyProvider là 7 hoặc 8 thì proxyType là bắt buộc.\n- Nếu proxyProvider là 17 thì country là bắt buộc; state và city là không bắt buộc.\n",
                    "example": 2
                  },
                  "proxyType": {
                    "type": "integer",
                    "format": "int32",
                    "description": "Loại giao thức proxy: 0=HTTP, 1=HTTPS, 2=SOCKS5, 3=SSH.\nChỉ bắt buộc khi proxyProvider là 7 hoặc 8.\n",
                    "example": 1
                  },
                  "refreshUrl": {
                    "type": "string",
                    "description": "URL để làm mới IP proxy động (nếu được hỗ trợ)",
                    "example": ""
                  },
                  "state": {
                    "type": "string",
                    "description": "Tỉnh/Thành",
                    "example": ""
                  },
                  "username": {
                    "type": "string",
                    "description": "Tên người dùng để xác thực proxy",
                    "example": ""
                  }
                },
                "required": [
                  "proxyIp",
                  "proxyPort",
                  "proxyProvider"
                ]
              },
              "example": {
                "proxyIp": "8.10.22.46",
                "proxyPort": 65534,
                "proxyProvider": 2,
                "proxyType": 1,
                "country": "AD",
                "state": "",
                "city": "",
                "encryptionType": 14,
                "ipChangeAction": 1,
                "ipMonitor": false,
                "proxyName": "sock5_{{random}}",
                "refreshUrl": "",
                "username": "",
                "password": ""
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Thành công. `data` là một giá trị số nguyên duy nhất.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "description": "Mã kết quả. `0` là thành công; các mã khác biểu thị ngoại lệ.",
                      "type": "integer"
                    },
                    "msg": {
                      "description": "Thông báo lỗi",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "description": "ID proxy",
                      "type": "integer"
                    },
                    "requestId": {
                      "description": "ID yêu cầu của thao tác",
                      "type": "string"
                    }
                  },
                  "required": [
                    "code",
                    "msg",
                    "data",
                    "requestId"
                  ]
                },
                "example": {
                  "code": 0,
                  "msg": "",
                  "data": 0,
                  "requestId": "abcd1234"
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/proxyInfo/update": {
      "post": {
        "summary": "Sửa đổi thông tin proxy",
        "deprecated": false,
        "description": "Cập nhật một cấu hình proxy.\n- **Điều kiện tiên quyết**: proxy phải tồn tại trong nhóm. **Không thể cập nhật proxy động** qua endpoint này. Dịch vụ thiết bị phải là một giá trị được nhận diện.\n- **Tác động**: đây là ghi đè toàn bộ, không phải cập nhật từng phần — các trường bạn bỏ trống sẽ trở về giá trị mặc định chứ không giữ giá trị hiện tại. Hãy đọc proxy ra trước rồi gửi lại đầy đủ.\n- **Phạm vi áp dụng**: các profile vẫn giữ liên kết vì chỉ bản ghi proxy thay đổi, nhưng một trình duyệt hoặc cloud phone đang chạy vẫn tiếp tục dùng thông tin kết nối mà nó đã khởi động cùng. Hãy khởi động lại nó để áp dụng thông tin mới.\n- **Hoàn tất**: đồng bộ. Không có khóa lạc quan, nên các lần cập nhật đồng thời sẽ lấy lần ghi cuối cùng.\n\nCác mã lỗi nghiệp vụ đã ghi nhận: `14003`, `14023`. Xem [Error Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi thao tác cũng có thể trả về các mã dùng chung.",
        "operationId": "updateProxyUsingPOST",
        "tags": [
          "Proxy Management"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id",
                  "proxyIp",
                  "proxyPort",
                  "proxyProvider"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "ID duy nhất của proxy"
                  },
                  "proxyIp": {
                    "type": "string",
                    "description": "Địa chỉ IP của máy chủ proxy"
                  },
                  "proxyPort": {
                    "type": "integer",
                    "description": "Cổng của máy chủ proxy"
                  },
                  "proxyName": {
                    "type": "string",
                    "description": "Tên tùy chỉnh cho proxy"
                  },
                  "country": {
                    "type": "string",
                    "description": "Quốc gia (ví dụ: \"US\", \"CN\")"
                  },
                  "city": {
                    "type": "string",
                    "description": "Tên thành phố"
                  },
                  "username": {
                    "type": "string",
                    "description": "Tên người dùng xác thực (nếu cần)"
                  },
                  "password": {
                    "type": "string",
                    "description": "Mật khẩu xác thực (nếu cần)"
                  },
                  "proxyType": {
                    "type": "integer",
                    "description": "Loại giao thức proxy:\n- `0` = HTTP\n- `1` = HTTPS\n- `2` = SOCKS5\n- `3` = SSH\n"
                  },
                  "proxyProvider": {
                    "type": "integer",
                    "description": "ID Loại proxy:\n- `0` = HTTP\n- `1` = HTTPS\n- `2` = SOCKS5\n- `3` = SSH\n- `4` = Oxylabs\n- `5` = Proxys.io\n- `6` = GeoSurf\n- `7` = Luminati\n- `8` = Lumauto\n- `9` = Oxylabsauto\n- `10` = Trojan\n- `11` = Shadowsocks\n- `12` = vmess\n- `13` = ABCPROXY\n- `14` = LunaProxy\n- `15` = IPHTML\n- `16` = PiaProxy\n- `17` = 922S5\n"
                  },
                  "encryptionType": {
                    "type": "integer",
                    "description": "Loại mã hóa (tùy dịch vụ thiết bị, thường là 0 nếu không mã hóa)"
                  },
                  "ipMonitor": {
                    "type": "boolean",
                    "description": "Bật theo dõi thay đổi IP"
                  },
                  "ipChangeAction": {
                    "type": "integer",
                    "description": "Hành động khi IP thay đổi:\n- `1` = Thông báo / Làm mới (giá trị thường dùng)\n> Xem tài liệu hệ thống để biết đầy đủ các tùy chọn.\n"
                  },
                  "refreshUrl": {
                    "type": "string",
                    "description": "URL để kích hoạt làm mới IP (nếu dịch vụ thiết bị hỗ trợ)"
                  },
                  "state": {
                    "type": "string",
                    "description": "Trường dự trữ hoặc trạng thái tùy chỉnh (không bắt buộc)"
                  }
                }
              },
              "example": {
                "id": "1566403036579198",
                "proxyIp": "108.11.23.85",
                "proxyPort": 4096,
                "proxyName": "MyProxy",
                "country": "US",
                "city": "New York",
                "username": "",
                "password": "",
                "proxyType": 0,
                "proxyProvider": 0,
                "encryptionType": 0,
                "ipMonitor": true,
                "ipChangeAction": 1,
                "refreshUrl": "",
                "state": ""
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Thành công. Thao tác này không trả về payload; kết quả được thể hiện qua `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "description": "Mã kết quả. `0` là thành công; các mã khác biểu thị ngoại lệ.",
                      "type": "integer"
                    },
                    "msg": {
                      "description": "Thông báo lỗi",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "requestId": {
                      "description": "ID yêu cầu của thao tác",
                      "type": "string"
                    }
                  },
                  "required": [
                    "code",
                    "msg",
                    "requestId"
                  ]
                },
                "example": {
                  "code": 0,
                  "msg": "",
                  "requestId": "efgh5678"
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/proxyInfo/delete": {
      "post": {
        "summary": "Xóa máy chủ proxy",
        "deprecated": false,
        "description": "Xóa một hoặc nhiều cấu hình proxy.\n- **Điều kiện tiên quyết**: ít nhất một ID phải tra cứu được trong nhóm; những ID không tra cứu được sẽ bị bỏ qua âm thầm. **Không thể xóa một proxy nền tảng cloud chưa hết hạn** — trường hợp này được báo bằng một mã lỗi riêng.\n- **Tác động**: proxy tự sở hữu bị loại bỏ và được hủy liên kết khỏi mọi jump server. Một proxy nền tảng cloud đã hết hạn thì thay vào đó được đánh dấu chờ giải phóng, đăng ký của nó bị hủy, và IP được giải phóng **không đồng bộ** — quá trình giải phóng có thể vẫn đang diễn ra khi phản hồi trả về.\n- **Ảnh hưởng lan truyền**: các profile và cloud phone đang gán proxy này **không bị chặn cũng không được hủy gán**. Chúng vẫn tiếp tục trỏ tới proxy đã bị xóa, và các phiên đang chạy không bị ngắt. Hãy gán lại chúng bằng `/env/setProxy/batch` trước khi xóa.\n- **Thử lại**: các ID được xử lý lần lượt và lệnh gọi dừng lại ở lần từ chối đầu tiên, nên lô có thể chỉ được áp dụng một phần. Hãy đọc lại `/proxyInfo/page` trước khi thử lại.\n\nCác mã lỗi nghiệp vụ đã ghi nhận: `14017`, `14024`. Xem [Error Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi thao tác cũng có thể trả về các mã dùng chung.",
        "operationId": "deleteProxyUsingPOST",
        "tags": [
          "Proxy Management"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "integer",
                  "format": "int64"
                }
              },
              "example": [
                1054661322597744600,
                1054661322597744600
              ]
            }
          }
        },
        "responses": {
          "200": {
            "description": "Thành công. Thao tác này không trả về payload; kết quả được thể hiện qua `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "description": "Mã kết quả. `0` là thành công; các mã khác biểu thị ngoại lệ.",
                      "type": "integer"
                    },
                    "msg": {
                      "description": "Thông báo lỗi",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "requestId": {
                      "description": "ID yêu cầu của thao tác",
                      "type": "string"
                    }
                  },
                  "required": [
                    "code",
                    "msg",
                    "requestId"
                  ]
                },
                "example": {
                  "code": 0,
                  "msg": "",
                  "requestId": "ijkl9012"
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/envgroup/page": {
      "post": {
        "operationId": "envgroupPagePost",
        "summary": "Lấy danh sách các nhóm",
        "deprecated": false,
        "description": "Liệt kê theo trang các nhóm profile.\n- **Tác động**: không có. Chỉ đọc. An toàn khi thử lại với thời gian chờ tăng dần có giới hạn.\n- **Lưu ý**: khi nhóm làm việc phân quyền theo từng nhóm, một thành viên chưa được cấp nhóm nào sẽ nhận một trang rỗng thay vì lỗi.",
        "tags": [
          "Group Management"
        ],
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "groupName": {
                    "type": "string",
                    "title": "Tên nhóm (khớp một phần)"
                  },
                  "pageNo": {
                    "type": "integer",
                    "title": "Trang hiện tại"
                  },
                  "pageSize": {
                    "type": "integer",
                    "title": "Số mục mỗi trang"
                  }
                },
                "required": [
                  "groupName",
                  "pageNo",
                  "pageSize"
                ]
              },
              "example": {
                "groupName": "132-kernel",
                "pageNo": 1,
                "pageSize": 10
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Thành công. `data.dataList` chứa các bản ghi của trang được yêu cầu và là mảng rỗng khi không có kết quả khớp; `data.total`, `data.current` và `data.pages` mô tả phân trang.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "description": "Mã kết quả. `0` là thành công; các mã khác biểu thị ngoại lệ.",
                      "type": "integer"
                    },
                    "msg": {
                      "description": "Thông báo lỗi",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "current": {
                          "type": "string"
                        },
                        "dataList": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "description": "ID nhóm",
                                "type": "string"
                              },
                              "groupName": {
                                "description": "Tên nhóm",
                                "type": "string"
                              }
                            }
                          }
                        },
                        "pages": {
                          "type": "string"
                        },
                        "total": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "current",
                        "dataList",
                        "pages",
                        "total"
                      ]
                    },
                    "requestId": {
                      "description": "ID yêu cầu của thao tác",
                      "type": "string"
                    }
                  },
                  "required": [
                    "code",
                    "msg",
                    "data",
                    "requestId"
                  ]
                },
                "example": {
                  "code": 0,
                  "msg": "",
                  "data": {
                    "current": "0",
                    "dataList": [
                      {
                        "id": "0",
                        "groupName": ""
                      }
                    ],
                    "pages": "0",
                    "total": "0"
                  },
                  "requestId": ""
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/envgroup/edit": {
      "post": {
        "operationId": "envgroupEditPost",
        "summary": "Chỉnh sửa thông tin nhóm",
        "deprecated": false,
        "description": "Đổi tên một nhóm profile.\n- **Điều kiện tiên quyết**: nhóm phải tồn tại trong nhóm làm việc, bên gọi phải có quyền với nó, và tên mới không được trùng với nhóm khác. Bước kiểm tra trùng tên chỉ chạy khi tên thực sự thay đổi.\n- **Tác động**: đổi tên nhóm. Các profile vẫn ở trong đó — chúng tham chiếu nhóm theo ID, nên không cần cập nhật gì khác.\n- **Hoàn tất**: đồng bộ và idempotent.",
        "tags": [
          "Group Management"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "groupName",
                  "id"
                ],
                "properties": {
                  "groupName": {
                    "type": "string",
                    "description": "Tên mới cho nhóm môi trường"
                  },
                  "id": {
                    "type": "string",
                    "description": "ID duy nhất của nhóm cần cập nhật"
                  }
                }
              },
              "example": {
                "groupName": "Group-1574544945848579",
                "id": "1574525425557584"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Thành công. `data` là một giá trị boolean duy nhất.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "description": "Mã kết quả. `0` là thành công; các mã khác biểu thị ngoại lệ.",
                      "type": "integer"
                    },
                    "msg": {
                      "description": "Thông báo lỗi",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "boolean",
                      "description": "Kết quả"
                    },
                    "requestId": {
                      "description": "ID yêu cầu của thao tác",
                      "type": "string"
                    }
                  },
                  "required": [
                    "code",
                    "msg",
                    "data",
                    "requestId"
                  ]
                },
                "example": {
                  "code": 0,
                  "msg": null,
                  "data": true,
                  "requestId": "0d8b6f1c-9f4e-4d02-9d1a-7c3f5a1b2c3d"
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/envgroup/create": {
      "post": {
        "operationId": "envgroupCreatePost",
        "summary": "Thêm nhóm",
        "deprecated": false,
        "description": "Tạo một nhóm profile.\n- **Điều kiện tiên quyết**: tên không được rỗng, tối đa 100 ký tự, và chưa được một nhóm khác trong nhóm làm việc sử dụng. **Mỗi nhóm làm việc bị giới hạn 1000 nhóm.**\n- **Tác động**: tạo nhóm. Khi nhóm làm việc phân quyền theo từng nhóm và bên gọi không phải chủ sở hữu, bên gọi cũng được cấp quyền truy cập nhóm mới.\n- **Hoàn tất**: đồng bộ; `data` mang ID của nhóm mới.\n- **Thử lại**: tên trùng sẽ bị từ chối chứ không dùng lại nhóm đã có, nên nếu bạn thử lại sau một lệnh gọi thực ra đã thành công nhưng bạn không nhận được phản hồi, lần thử lại sẽ báo lỗi trùng tên — chính đó là dấu hiệu lần gọi đầu đã thành công. Hãy xác nhận bằng `/envgroup/page`.",
        "tags": [
          "Group Management"
        ],
        "parameters": [],
        "security": [
          {
            "Authorization": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {}
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "Thành công. `data` là một đối tượng với các trường `id` và `groupName`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "description": "Mã kết quả. `0` là thành công; các mã khác biểu thị ngoại lệ.",
                      "type": "integer"
                    },
                    "msg": {
                      "description": "Thông báo lỗi",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "description": "Dữ liệu do thao tác trả về",
                      "type": "object",
                      "properties": {
                        "id": {
                          "description": "ID nhóm",
                          "type": "string"
                        },
                        "groupName": {
                          "description": "Tên nhóm",
                          "type": "string"
                        }
                      }
                    },
                    "requestId": {
                      "description": "ID yêu cầu của thao tác",
                      "type": "string"
                    }
                  },
                  "required": [
                    "code",
                    "msg",
                    "data",
                    "requestId"
                  ]
                },
                "example": {
                  "code": 0,
                  "msg": null,
                  "data": {
                    "id": "1673823102599682",
                    "groupName": "example-name"
                  },
                  "requestId": "0d8b6f1c-9f4e-4d02-9d1a-7c3f5a1b2c3d"
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/envgroup/delete": {
      "post": {
        "operationId": "envgroupDeletePost",
        "summary": "Xóa nhóm",
        "deprecated": false,
        "description": "Xóa một hoặc nhiều nhóm profile.\n- **Điều kiện tiên quyết**: ít nhất một ID phải tra cứu được trong nhóm làm việc và bên gọi phải có quyền với nó; các ID không tra cứu được sẽ bị bỏ qua âm thầm.\n- **Tác động**: xóa các nhóm và quyền truy cập đã cấp cho thành viên theo chúng. Các profile trình duyệt và cloud phone từng ở trong đó sẽ trở thành chưa thuộc nhóm nào, nên không để lại tham chiếu nhóm mồ côi. Bật cờ xóa luôn môi trường sẽ chuyển thêm các profile mà bạn có quyền **vào thùng rác** — chúng không bị hủy, và cờ này không bao giờ tác động tới cloud phone.\n- **Hoàn tất**: đồng bộ nhưng **không nguyên tử**. Nhóm bị xóa trước, và các bước sau vẫn có thể thất bại, chẳng hạn khi một profile đang chạy hoặc đang chuyển giao, để lại kết quả là nhóm đã mất mà profile chưa được chuyển đi.\n- **Thử lại**: hãy kiểm chứng bằng `/envgroup/page` và danh sách profile trước khi thử lại.\n\nCác mã lỗi nghiệp vụ đã ghi nhận: `19064`. Xem [Error Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi thao tác cũng có thể trả về các mã dùng chung.",
        "tags": [
          "Group Management"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ids"
                ],
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "ID duy nhất của nhóm cần cập nhật"
                  },
                  "isDeleteAllEnv": {
                    "type": "boolean",
                    "description": "Xóa các môi trường trong nhóm? false: không xóa; true: xóa"
                  }
                }
              },
              "example": {
                "ids": [
                  "string"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Thành công. `data` là một giá trị boolean duy nhất.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "description": "Mã kết quả. `0` là thành công; các mã khác biểu thị ngoại lệ.",
                      "type": "integer"
                    },
                    "msg": {
                      "description": "Thông báo lỗi",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "boolean"
                    },
                    "requestId": {
                      "description": "ID yêu cầu của thao tác",
                      "type": "string"
                    }
                  },
                  "required": [
                    "code",
                    "msg",
                    "data",
                    "requestId"
                  ]
                },
                "example": {
                  "code": 0,
                  "msg": "",
                  "data": true,
                  "requestId": ""
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/envtag/all": {
      "get": {
        "operationId": "envtagAllGet",
        "summary": "Lấy danh sách thẻ",
        "deprecated": false,
        "description": "Trả về mọi thẻ profile trong nhóm.\n- **Tác động**: không có. Chỉ đọc. An toàn khi thử lại với thời gian chờ tăng dần có giới hạn.\n- **Lưu ý**: endpoint này **không phân trang và thẻ không có giới hạn phía server**, nên phản hồi sẽ lớn dần theo số thẻ mà nhóm bạn đã tạo. Hãy thiết kế client tương ứng.",
        "tags": [
          "Tag Management"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Thành công. `data` là mảng các đối tượng với các trường `id` và `tagName`, và là mảng rỗng khi không có gì để trả về.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "description": "Mã kết quả. `0` là thành công; các mã khác biểu thị ngoại lệ.",
                      "type": "integer"
                    },
                    "msg": {
                      "description": "Thông báo lỗi",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "description": "ID thẻ",
                            "type": "string"
                          },
                          "tagName": {
                            "description": "Tên thẻ",
                            "type": "string"
                          }
                        }
                      }
                    },
                    "requestId": {
                      "description": "ID yêu cầu của thao tác",
                      "type": "string"
                    }
                  },
                  "required": [
                    "code",
                    "msg",
                    "data",
                    "requestId"
                  ]
                },
                "example": {
                  "code": 0,
                  "msg": "",
                  "data": [
                    {
                      "id": "0",
                      "tagName": ""
                    }
                  ],
                  "requestId": ""
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/envtag/create": {
      "post": {
        "operationId": "envtagCreatePost",
        "summary": "Tạo thẻ",
        "deprecated": false,
        "description": "Tạo một thẻ profile.\n- **Điều kiện tiên quyết**: tên không được rỗng, tối đa 100 ký tự, và chưa được một thẻ khác trong nhóm sử dụng.\n- **Tác động**: tạo thẻ và trả về ID của nó. Khác với nhóm, thẻ không có giới hạn số lượng.\n- **Thử lại**: tên trùng sẽ bị từ chối chứ không dùng lại thẻ đã có. Hãy xác nhận bằng `/envtag/all`.",
        "tags": [
          "Tag Management"
        ],
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tagName": {
                    "type": "string",
                    "description": "tên thẻ"
                  }
                }
              },
              "example": {
                "tagName": "Tag-123"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Thành công. `data` là một đối tượng với các trường `id` và `tagName`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "description": "Mã kết quả. `0` là thành công; các mã khác biểu thị ngoại lệ.",
                      "type": "integer"
                    },
                    "msg": {
                      "description": "Thông báo lỗi",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "description": "ID thẻ",
                          "type": "string"
                        },
                        "tagName": {
                          "description": "Tên thẻ",
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "tagName"
                      ]
                    },
                    "requestId": {
                      "description": "ID yêu cầu của thao tác",
                      "type": "string"
                    },
                    "error": {
                      "type": "object",
                      "properties": {}
                    }
                  },
                  "required": [
                    "code",
                    "msg",
                    "data",
                    "requestId"
                  ]
                },
                "example": {
                  "code": 0,
                  "msg": "",
                  "data": {
                    "id": "0",
                    "tagName": ""
                  },
                  "requestId": ""
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/envtag/edit": {
      "post": {
        "operationId": "envtagEditPost",
        "summary": "Chỉnh sửa thông tin của thẻ",
        "deprecated": false,
        "description": "Đổi tên một thẻ profile.\n- **Điều kiện tiên quyết**: thẻ phải tồn tại trong nhóm, và tên mới không được trùng với thẻ khác.\n- **Tác động**: đổi tên thẻ. Các liên kết thẻ hiện có không bị ảnh hưởng và các profile hiển thị tên mới ngay lập tức.\n- **Hoàn tất**: đồng bộ và idempotent.",
        "tags": [
          "Tag Management"
        ],
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "ID thẻ"
                  },
                  "tagName": {
                    "type": "string",
                    "description": "tên thẻ"
                  }
                }
              },
              "example": {
                "id": "1564715845550177",
                "tagName": "Updated-123"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Thành công. `data` là một giá trị boolean duy nhất.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "description": "Mã kết quả. `0` là thành công; các mã khác biểu thị ngoại lệ.",
                      "type": "integer"
                    },
                    "msg": {
                      "description": "Thông báo lỗi",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "boolean"
                    },
                    "requestId": {
                      "description": "ID yêu cầu của thao tác",
                      "type": "string"
                    },
                    "error": {
                      "type": "object",
                      "properties": {}
                    }
                  },
                  "required": [
                    "code",
                    "msg",
                    "data",
                    "requestId"
                  ]
                },
                "example": {
                  "code": 0,
                  "msg": "",
                  "data": true,
                  "requestId": ""
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/envtag/delete": {
      "post": {
        "operationId": "envtagDeletePost",
        "summary": "Xóa thẻ",
        "deprecated": false,
        "description": "Xóa một hoặc nhiều thẻ profile.\n- **Điều kiện tiên quyết**: ít nhất một ID phải tra cứu được trong nhóm; những ID không tra cứu được sẽ bị bỏ qua âm thầm.\n- **Tác động**: xóa các thẻ. Thẻ biến mất khỏi mọi profile từng mang nó, và không profile nào bị xóa. Endpoint này chỉ quản lý thẻ profile — thẻ tệp Cloud Storage được quản lý riêng.\n- **Hoàn tất**: đồng bộ. Gọi lại sẽ báo các thẻ không hợp lệ, điều đó nghĩa là lần gọi đầu đã thành công.\n\nCác mã lỗi nghiệp vụ đã ghi nhận: `19105`. Xem [Error Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi thao tác cũng có thể trả về các mã dùng chung.",
        "tags": [
          "Tag Management"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ids"
                ],
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "ID duy nhất của thẻ cần cập nhật"
                  }
                }
              },
              "example": {
                "ids": [
                  "string"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Thành công. `data` là một giá trị boolean duy nhất.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "description": "Mã kết quả. `0` là thành công; các mã khác biểu thị ngoại lệ.",
                      "type": "integer"
                    },
                    "msg": {
                      "description": "Thông báo lỗi",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "boolean"
                    },
                    "requestId": {
                      "description": "ID yêu cầu của thao tác",
                      "type": "string"
                    }
                  },
                  "required": [
                    "code",
                    "msg",
                    "data",
                    "requestId"
                  ]
                },
                "example": {
                  "code": 0,
                  "msg": "",
                  "data": true,
                  "requestId": ""
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/webhook/config": {
      "post": {
        "operationId": "webhookConfigPost",
        "summary": "Cấu hình webhook của nhóm",
        "deprecated": false,
        "description": "Tạo mới hoặc thay thế cấu hình webhook của nhóm.\n\n- **Điều kiện tiên quyết**: `callbackUrl` phải là URL HTTPS dài từ 1 đến 1024 ký tự, có tên host, không chứa thông tin người dùng, không có fragment và cổng hợp lệ. Mọi địa chỉ mà nó phân giải ra đều phải định tuyến được trên internet; các dải localhost, riêng tư, link-local, CGNAT, metadata đám mây, dải dành riêng cho giao thức, dải dùng cho tài liệu và 6to4 sẽ bị từ chối. Cùng phép kiểm tra đó được thực hiện lại khi một lần gửi mở kết nối, nên một tên miền sau này phân giải vào dải riêng tư chỉ làm lần gửi đó thất bại chứ không chạm tới mạng của bạn.\n- **Tác động**: upsert cấu hình duy nhất thuộc nhóm trong ngữ cảnh phía máy chủ. `teamId` không bao giờ được đọc từ request, nên người gọi không thể cấu hình cho nhóm khác. Endpoint của Local API và Open API đọc và ghi cùng một bản ghi, và phản hồi phản ánh nội dung đã lưu.\n- **Tắt**: `enabled: false` giữ lại URL và dừng việc tạo sự kiện mới. Các sự kiện đã nằm trong hàng đợi sẽ bị hủy lần lượt nếu cấu hình vẫn đang tắt khi chúng được cấp phép lần kế tiếp, nên bật lại sớm sẽ cho chúng tiếp tục.\n- **Thử lại**: an toàn. Việc lưu là upsert theo nhóm, nên gửi lại cùng một request sẽ hội tụ về cùng một trạng thái.\n\nXem [Webhooks](../Getting%20Started/webhooks.md) để biết hợp đồng callback, tên sự kiện và lịch thử lại.\n\nCác mã lỗi nghiệp vụ đã ghi nhận: `41001`. Xem [Error Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi thao tác cũng có thể trả về các mã dùng chung.",
        "tags": [
          "Webhook Management"
        ],
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "callbackUrl": {
                    "description": "URL HTTPS nhận sự kiện. Từ 1 đến 1024 ký tự.",
                    "type": "string",
                    "maxLength": 1024
                  },
                  "enabled": {
                    "description": "Việc gửi sự kiện có được bật cho nhóm hay không.",
                    "type": "boolean"
                  }
                },
                "required": [
                  "callbackUrl",
                  "enabled"
                ]
              },
              "example": {
                "callbackUrl": "https://example.com/morelogin/webhook",
                "enabled": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Thành công. `data` là một đối tượng có các trường `callbackUrl` và `enabled`, phản ánh cấu hình đã lưu.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "description": "Mã kết quả. `0` là thành công; các mã khác biểu thị ngoại lệ.",
                      "type": "integer"
                    },
                    "msg": {
                      "description": "Thông báo lỗi",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "callbackUrl": {
                          "description": "URL callback HTTPS đã lưu.",
                          "type": "string"
                        },
                        "enabled": {
                          "description": "Việc gửi sự kiện có được bật cho nhóm hay không.",
                          "type": "boolean"
                        }
                      }
                    },
                    "requestId": {
                      "description": "ID yêu cầu của thao tác",
                      "type": "string"
                    }
                  },
                  "required": [
                    "code",
                    "msg",
                    "data",
                    "requestId"
                  ]
                },
                "example": {
                  "code": 0,
                  "msg": "",
                  "data": {
                    "callbackUrl": "https://example.com/morelogin/webhook",
                    "enabled": true
                  },
                  "requestId": ""
                }
              }
            },
            "headers": {}
          }
        }
      }
    }
  },
  "webhooks": {
    "cloud_phone.power_on": {
      "post": {
        "operationId": "webhookCloudPhonePowerOn",
        "summary": "Đã hoàn tất bật cloud phone",
        "description": "Được gửi khi một lần bật đã có kết quả cuối cùng.\n\n`success` chỉ là `true` khi callback của dịch vụ thiết bị thành công và việc đồng bộ cục bộ đã commit. Lỗi tính phí khi bật, hoặc một lần kiểm tra proxy bất đồng bộ thất bại và hoàn tất dọn dẹp, đều báo là bật thất bại và không tạo thêm sự kiện tắt riêng. Các việc hoãn lại như cài ứng dụng mặc định không làm thay đổi kết quả.\n\nNếu quá trình bật bị một thao tác tắt làm gián đoạn thì lần thử đó không tạo sự kiện bật; lần tắt thành công sau đó vẫn tạo `cloud_phone.power_off` riêng.",
        "tags": [
          "Webhook Management"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookEventType"
          },
          {
            "$ref": "#/components/parameters/WebhookUserAgent"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CloudPhoneWebhookEvent"
              },
              "example": {
                "eventId": "c7f3a2b10d8e4f6a9c1b2d3e4f5a6b7c",
                "eventType": "cloud_phone.power_on",
                "eventTime": "1787241600000",
                "data": {
                  "teamId": "10001",
                  "cloudPhoneId": "20001"
                },
                "result": {
                  "success": false,
                  "code": "33318",
                  "message": "Operation failed"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "cloud_phone.power_off": {
      "post": {
        "operationId": "webhookCloudPhonePowerOff",
        "summary": "Cloud phone đã tắt",
        "description": "Được gửi khi một lần tắt thành công. Lần tắt thất bại không tạo sự kiện này, nên `result.success` ở đây luôn là `true`.\n\n`data.reason` ghi lại lý do máy dừng.",
        "tags": [
          "Webhook Management"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookEventType"
          },
          {
            "$ref": "#/components/parameters/WebhookUserAgent"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CloudPhonePowerOffWebhookEvent"
              },
              "example": {
                "eventId": "5b1d0c9e7a2f4d38b6c5a4e3f2d1c0b9",
                "eventType": "cloud_phone.power_off",
                "eventTime": "1787241600000",
                "data": {
                  "teamId": "10001",
                  "cloudPhoneId": "20001",
                  "reason": "MONEY_SAVING"
                },
                "result": {
                  "success": true,
                  "code": "0",
                  "message": null
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "cloud_phone.restart": {
      "post": {
        "operationId": "webhookCloudPhoneRestart",
        "summary": "Đã hoàn tất khởi động lại cloud phone",
        "description": "Được gửi khi một lần khởi động lại đã có kết quả cuối cùng. `success` chỉ là `true` khi callback của dịch vụ thiết bị thành công và việc đồng bộ cục bộ đã commit.",
        "tags": [
          "Webhook Management"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookEventType"
          },
          {
            "$ref": "#/components/parameters/WebhookUserAgent"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CloudPhoneWebhookEvent"
              },
              "example": {
                "eventId": "9e4c1a7b3f2d40518a6b5c4d3e2f1a0b",
                "eventType": "cloud_phone.restart",
                "eventTime": "1787241600000",
                "data": {
                  "teamId": "10001",
                  "cloudPhoneId": "20001"
                },
                "result": {
                  "success": true,
                  "code": "0",
                  "message": null
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "cloud_phone.reset": {
      "post": {
        "operationId": "webhookCloudPhoneReset",
        "summary": "Đã hoàn tất đặt lại cloud phone",
        "description": "Được gửi khi một lần đặt lại đã có kết quả cuối cùng. `success` chỉ là `true` khi callback của dịch vụ thiết bị thành công và việc đồng bộ cục bộ đã commit.",
        "tags": [
          "Webhook Management"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookEventType"
          },
          {
            "$ref": "#/components/parameters/WebhookUserAgent"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CloudPhoneWebhookEvent"
              },
              "example": {
                "eventId": "2f8a6d4c0b9e47138c7d6e5f4a3b2c1d",
                "eventType": "cloud_phone.reset",
                "eventTime": "1787241600000",
                "data": {
                  "teamId": "10001",
                  "cloudPhoneId": "20001"
                },
                "result": {
                  "success": true,
                  "code": "0",
                  "message": null
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "cloud_phone.new_machine": {
      "post": {
        "operationId": "webhookCloudPhoneNewMachine",
        "summary": "Đã hoàn tất tạo máy mới một lần bấm cho cloud phone",
        "description": "Được gửi khi thao tác tạo máy mới một lần bấm đã có kết quả cuối cùng. `success` chỉ là `true` khi callback của dịch vụ thiết bị thành công và việc đồng bộ cục bộ đã commit.",
        "tags": [
          "Webhook Management"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookEventType"
          },
          {
            "$ref": "#/components/parameters/WebhookUserAgent"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CloudPhoneWebhookEvent"
              },
              "example": {
                "eventId": "7c3b1e9d5a4f42068b1c2d3e4f5a6b70",
                "eventType": "cloud_phone.new_machine",
                "eventTime": "1787241600000",
                "data": {
                  "teamId": "10001",
                  "cloudPhoneId": "20001"
                },
                "result": {
                  "success": true,
                  "code": "0",
                  "message": null
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "cloud_browser.start": {
      "post": {
        "operationId": "webhookCloudBrowserStart",
        "summary": "Đã hoàn tất mở cloud browser",
        "description": "Được gửi khi việc mở cloud browser đã có kết quả cuối cùng.",
        "tags": [
          "Webhook Management"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookEventType"
          },
          {
            "$ref": "#/components/parameters/WebhookUserAgent"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CloudBrowserWebhookEvent"
              },
              "example": {
                "eventId": "4a9f2c7e1b8d43509c6a5b4c3d2e1f00",
                "eventType": "cloud_browser.start",
                "eventTime": "1787241600000",
                "data": {
                  "teamId": "10001",
                  "cloudBrowserId": "30001",
                  "runId": "40001"
                },
                "result": {
                  "success": true,
                  "code": "0",
                  "message": null
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "cloud_browser.stop": {
      "post": {
        "operationId": "webhookCloudBrowserStop",
        "summary": "Đã hoàn tất đóng cloud browser",
        "description": "Được gửi cho mọi lần thử đóng cloud browser, dù thành công hay không. Việc chuyển sang trạng thái đóng thất bại tự nó đã là kết quả thất bại của lần thử đó; lần thử lại thủ công sau đó hoặc lần giải phóng cưỡng bức được coi là thao tác mới và sinh ra `eventId` mới.\n\nSự kiện được loại trùng theo từng thao tác đóng, nên một lần thử không bao giờ sinh ra hai sự kiện và hai lần thử không bao giờ bị gộp. Lỗi lưu trữ nội bộ chỉ ảnh hưởng tới `result`, không bao giờ ghi đè `reason` đã chốt khi bắt đầu đóng.",
        "tags": [
          "Webhook Management"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookEventType"
          },
          {
            "$ref": "#/components/parameters/WebhookUserAgent"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CloudBrowserStopWebhookEvent"
              },
              "example": {
                "eventId": "c7f3a2b10d8e4f6a9c1b2d3e4f5a6b7c",
                "eventType": "cloud_browser.stop",
                "eventTime": "1787241600000",
                "data": {
                  "teamId": "10001",
                  "cloudBrowserId": "30001",
                  "runId": "40001",
                  "reason": "BROWSER_EXITED"
                },
                "result": {
                  "success": false,
                  "code": "40006",
                  "message": "Operation failed"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "Authorization": {
        "type": "http",
        "description": "Giá trị của trường access_token trong phản hồi của giao diện lấy access token",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      }
    },
    "parameters": {
      "WebhookEventId": {
        "name": "X-Webhook-Event-Id",
        "in": "header",
        "required": true,
        "description": "Cùng giá trị với `eventId` trong body. Dùng nó để xử lý của bạn có tính idempotent.",
        "schema": {
          "type": "string"
        }
      },
      "WebhookEventType": {
        "name": "X-Webhook-Event-Type",
        "in": "header",
        "required": true,
        "description": "Cùng giá trị với `eventType` trong body, để bạn định tuyến mà không cần phân tích body.",
        "schema": {
          "type": "string"
        }
      },
      "WebhookUserAgent": {
        "name": "User-Agent",
        "in": "header",
        "required": true,
        "description": "Luôn là `MoreLogin-Webhook/1.0`.",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "WebhookAck": {
        "description": "Mọi mã 2xx đều được coi là đã gửi thành công. Body phản hồi bị bỏ qua, nên hãy trả về càng sớm càng tốt và xử lý bất đồng bộ. Mọi trường hợp khác, kể cả timeout kết nối hoặc timeout đọc, đều tính là thất bại và sẽ được thử lại."
      }
    },
    "schemas": {
      "WebhookResult": {
        "type": "object",
        "description": "Kết quả cuối cùng của thao tác mà sự kiện mô tả.",
        "properties": {
          "success": {
            "description": "Thao tác cuối cùng có thành công hay không.",
            "type": "boolean"
          },
          "code": {
            "description": "`0` khi thành công. Khi thất bại là một mã lỗi nghiệp vụ ổn định, không phụ thuộc ngôn ngữ.",
            "type": "string"
          },
          "message": {
            "description": "`null` khi thành công. Khi thất bại là phần giải thích theo ngôn ngữ của nhóm, và quay về `en-US` nếu ngôn ngữ đó hoặc nội dung của nó không có.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "success",
          "code",
          "message"
        ]
      },
      "CloudPhoneWebhookEvent": {
        "type": "object",
        "properties": {
          "eventId": {
            "description": "ID sự kiện duy nhất. Các lần thử gửi lại và phát lại nội bộ của cùng sự kiện đều dùng lại nó, nên hãy coi đây là khóa idempotency.",
            "type": "string"
          },
          "eventType": {
            "description": "Tên sự kiện ổn định.",
            "type": "string"
          },
          "eventTime": {
            "description": "Thời điểm kết quả nghiệp vụ cuối cùng xảy ra, tính bằng milli giây epoch Unix. Được gửi dưới dạng chuỗi.",
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "teamId": {
                "description": "Nhóm sở hữu tài nguyên này.",
                "type": "string"
              },
              "cloudPhoneId": {
                "description": "Cloud phone mà sự kiện này nói về.",
                "type": "string"
              }
            },
            "required": [
              "teamId",
              "cloudPhoneId"
            ]
          },
          "result": {
            "$ref": "#/components/schemas/WebhookResult"
          }
        },
        "required": [
          "eventId",
          "eventType",
          "eventTime",
          "data",
          "result"
        ]
      },
      "CloudPhonePowerOffWebhookEvent": {
        "type": "object",
        "properties": {
          "eventId": {
            "description": "ID sự kiện duy nhất. Các lần thử gửi lại và phát lại nội bộ của cùng sự kiện đều dùng lại nó, nên hãy coi đây là khóa idempotency.",
            "type": "string"
          },
          "eventType": {
            "description": "Tên sự kiện ổn định.",
            "type": "string"
          },
          "eventTime": {
            "description": "Thời điểm kết quả nghiệp vụ cuối cùng xảy ra, tính bằng milli giây epoch Unix. Được gửi dưới dạng chuỗi.",
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "teamId": {
                "description": "Nhóm sở hữu tài nguyên này.",
                "type": "string"
              },
              "cloudPhoneId": {
                "description": "Cloud phone mà sự kiện này nói về.",
                "type": "string"
              },
              "reason": {
                "description": "Lý do máy dừng.",
                "type": "string",
                "enum": [
                  "NORMAL",
                  "MONEY_SAVING",
                  "OTHER"
                ]
              }
            },
            "required": [
              "teamId",
              "cloudPhoneId",
              "reason"
            ]
          },
          "result": {
            "$ref": "#/components/schemas/WebhookResult"
          }
        },
        "required": [
          "eventId",
          "eventType",
          "eventTime",
          "data",
          "result"
        ]
      },
      "CloudBrowserWebhookEvent": {
        "type": "object",
        "properties": {
          "eventId": {
            "description": "ID sự kiện duy nhất. Các lần thử gửi lại và phát lại nội bộ của cùng sự kiện đều dùng lại nó, nên hãy coi đây là khóa idempotency.",
            "type": "string"
          },
          "eventType": {
            "description": "Tên sự kiện ổn định.",
            "type": "string"
          },
          "eventTime": {
            "description": "Thời điểm kết quả nghiệp vụ cuối cùng xảy ra, tính bằng milli giây epoch Unix. Được gửi dưới dạng chuỗi.",
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "teamId": {
                "description": "Nhóm sở hữu tài nguyên này.",
                "type": "string"
              },
              "cloudBrowserId": {
                "description": "Profile cloud browser mà sự kiện này nói về.",
                "type": "string"
              },
              "runId": {
                "description": "Phiên chạy mà sự kiện này nói về.",
                "type": "string"
              }
            },
            "required": [
              "teamId",
              "cloudBrowserId",
              "runId"
            ]
          },
          "result": {
            "$ref": "#/components/schemas/WebhookResult"
          }
        },
        "required": [
          "eventId",
          "eventType",
          "eventTime",
          "data",
          "result"
        ]
      },
      "CloudBrowserStopWebhookEvent": {
        "type": "object",
        "properties": {
          "eventId": {
            "description": "ID sự kiện duy nhất. Các lần thử gửi lại và phát lại nội bộ của cùng sự kiện đều dùng lại nó, nên hãy coi đây là khóa idempotency.",
            "type": "string"
          },
          "eventType": {
            "description": "Tên sự kiện ổn định.",
            "type": "string"
          },
          "eventTime": {
            "description": "Thời điểm kết quả nghiệp vụ cuối cùng xảy ra, tính bằng milli giây epoch Unix. Được gửi dưới dạng chuỗi.",
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "teamId": {
                "description": "Nhóm sở hữu tài nguyên này.",
                "type": "string"
              },
              "cloudBrowserId": {
                "description": "Profile cloud browser mà sự kiện này nói về.",
                "type": "string"
              },
              "runId": {
                "description": "Phiên chạy mà sự kiện này nói về.",
                "type": "string"
              },
              "reason": {
                "description": "Lý do phiên chạy dừng.",
                "type": "string",
                "enum": [
                  "NORMAL",
                  "MONEY_SAVING",
                  "ENERGY_SAVING",
                  "BROWSER_EXITED",
                  "OTHER"
                ]
              }
            },
            "required": [
              "teamId",
              "cloudBrowserId",
              "runId",
              "reason"
            ]
          },
          "result": {
            "$ref": "#/components/schemas/WebhookResult"
          }
        },
        "required": [
          "eventId",
          "eventType",
          "eventTime",
          "data",
          "result"
        ]
      }
    }
  },
  "servers": [
    {
      "url": "https://api.morelogin.com",
      "description": "Endpoint của MoreLogin Open API"
    }
  ],
  "security": [
    {
      "Authorization": []
    }
  ]
}